﻿using IQIGame.Onigao.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace IQIGame.Onigao.GamePlay
{
    /// <summary>
    /// 利用本地ip作为额外标识, 范围0-1023(最后一个网段255*4), 占用10位byte, 
    /// serverId固定为0, 服务器的才能用
    /// 1位存储符号位固定为0,11位存储服务器id,31位存储时间（秒）,10位存额外标识, 11位存储自增id,每秒最多生成2^11个id,否则ID会重复<br>
    /// </summary>
    public class LocalGUIDAllocator
    {
        public class DBIDPreSecondCountLimitException : Exception
        {
            public DBIDPreSecondCountLimitException() : base("pre sec alloc id count limit")
            {
            }
        }

        private static LocalGUIDAllocator _instance;
        public static LocalGUIDAllocator Instance
        {
            get
            {
                return _instance ??= new LocalGUIDAllocator();
            }
        }
        private LocalGUIDAllocator()
        {
            serverId = 0;
            exTag = GetExTag();
            if(exTag > 1023 || exTag < 0)
            {
                LogGame.LogError($"error exTag = {exTag}");
            }
        }

        private static int GetExTag()
        {
            try
            {
                string localIp = Dns.GetHostAddresses(Dns.GetHostName())
                    .First(e => e.AddressFamily == AddressFamily.InterNetwork)
                    .ToString();
                byte thr = byte.Parse(localIp.Split(".")[2]);
                byte four = byte.Parse(localIp.Split(".")[3]);
                return (thr - 31) * 256 + four;//因为公司的第三网段是31,32,33,34
            } catch(Exception e)
            {
                LogGame.LogError(e);
                return -1;
            }
            
        }

        public static readonly long START_TIME = 1391650200000L;
        /// <summary>
        /// 每秒最多生成id数
        /// </summary>
        public static readonly int MAX_PRE_SECOND_CNT = 2048;// 2048=2^11;
        private readonly object _lock = new object();
        /// <summary>
        /// 允许id溢出后回绕
        /// </summary>
        private long id = 0;
        /// <summary>
        /// 不能超过2^11
        /// </summary>
        private readonly long serverId;
        /// <summary>
        /// 额外标识
        /// </summary>
        private readonly long exTag;
        /// <summary>
        /// 记录上次id生成时间（秒）
        /// </summary>
        private int lastTimeSecond = 0;
        /// <summary>
        /// 记录本秒内生成的id数量
        /// </summary>
        private long currentSecondAllocIdCnt = 0;
        /// <summary>
        /// 每秒最大的生成id数量
        /// </summary>
        private long maxPreSecondAllocIdCnt = 0;
        /// <summary>
        /// 时钟回拨记录最后一次生成时间
        /// </summary>
        private int clockMovedBackwards_lastTimeSecond = 0;
        /// <summary>
        /// 分配id，如果超过每秒分配数量上限则抛出异常
        /// </summary>
        /// <returns></returns>
        public long AllocId()
        {
            return AllocId(false);
        }

        /// <summary>
        /// 分配id，如果超过每秒分配数量上限则thread.sleep到下一秒
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public long AllocAndWaitId()
        {
            try
            {
                return AllocId(true);
            }
            catch (DBIDPreSecondCountLimitException e)
            {
                throw new Exception(e.Message, e);
            }
        }

        private long GetSystemTimeStamp()
        {
            return DateTimeOffset.Now.ToUnixTimeMilliseconds() - START_TIME;
        }

        private int GetNewSecondTimeStamp()
        {
            int realTime = (int)(GetSystemTimeStamp() / 1000);
            if (clockMovedBackwards_lastTimeSecond != 0)
            {
                if (realTime <= clockMovedBackwards_lastTimeSecond)
                {
                    //时钟回拨了, 需要借用未来的时间
                    return clockMovedBackwards_lastTimeSecond;
                }
                else
                {
                    //系统时间已经追回
                    clockMovedBackwards_lastTimeSecond = 0;
                    return realTime;
                }
            }
            return realTime;
            
        }

        /// <summary>
        /// 返回分配的id总数量
        /// </summary>
        /// <returns></returns>
        public long GetAllocIdCount()
        {
            return id;
        }

        /// <summary>
        /// 分配id
        /// </summary>
        /// <param name="wait">如果为true，则超过每秒最大分配数量thread.sleep到下一秒</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        /// <exception cref="DBIDPreSecondCountLimitException"></exception>
        private long AllocId(bool wait)
        {
            if (exTag == 0)
            {
                throw new Exception("not init");
            }
            lock (_lock)
            {
                int newTimeSecond = GetNewSecondTimeStamp();
                if (newTimeSecond == lastTimeSecond)
                {
                    if (currentSecondAllocIdCnt > MAX_PRE_SECOND_CNT - 1)
                    {
                        if (clockMovedBackwards_lastTimeSecond != 0)
                        {
                            //处理系统时间回拨中, 直接修改回拨记录时间
                            newTimeSecond = ++clockMovedBackwards_lastTimeSecond;
                            ResetStatus(newTimeSecond);
                        }
                        else
                        {
                            if (wait)
                            {
                                long lockTime = 1000 - GetSystemTimeStamp() % 1000;
                                LogGame.LogWarning("allocAndWaitId thread sleep " + lockTime);
                                System.Threading.Thread.Sleep((int)lockTime);
                                newTimeSecond++;
                                ResetStatus(newTimeSecond);
                            }
                            else
                            {
                                throw new DBIDPreSecondCountLimitException();
                            }
                        }
                    }
                }
                else
                {
                    if (newTimeSecond < lastTimeSecond)
                    {
                        //时钟回拨, ntp同步会出现
                        clockMovedBackwards_lastTimeSecond = lastTimeSecond;
                        return AllocId(wait);
                    }
                    ResetStatus(newTimeSecond);
                }
                currentSecondAllocIdCnt++;
                return 0L << (64 - 1) 
                    | (serverId & 0x7FF) << (64 - 1 - 11) 
                    | (((long)newTimeSecond) & 0x7FFFFFFF) << (64 - 1 - 11 - 31)
                    | (exTag & 0x3FF << (64 - 1 - 11 - 31 - 10))
                    | ++id & 0x7FF;
            }
        }

        private void ResetStatus(int newSecond)
        {
            if (currentSecondAllocIdCnt > maxPreSecondAllocIdCnt)
            {
                maxPreSecondAllocIdCnt = currentSecondAllocIdCnt;
                LogGame.Log("maxPreSecondAllocIdCnt update " + maxPreSecondAllocIdCnt);
            }
            currentSecondAllocIdCnt = 0;
            lastTimeSecond = newSecond;
        }


        public static void Test()
        {
            int testCnt = MAX_PRE_SECOND_CNT * 1;
            HashSet<long> idList = new HashSet<long>(testCnt);
            LocalGUIDAllocator dbid = new LocalGUIDAllocator();
            UnityEngine.Debug.LogWarning("start test1");
            long start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
            int errCnt = 0;
            for (int i = 0; i < testCnt; i++)
            {
                try
                {
                    long id = dbid.AllocId();
                    if (id != 0 && !idList.Add(id))
                    {
                        UnityEngine.Debug.LogWarning("dup :" + id);
                    }
                }
                catch (DBIDPreSecondCountLimitException)
                {
                    errCnt++;
                }
            }
            UnityEngine.Debug.LogWarning("如果testCnt>MAX_PRE_SECOND_CNT这里是正常的 errCnt:" + errCnt);
            UnityEngine.Debug.LogWarning("time :" + (DateTimeOffset.Now.ToUnixTimeMilliseconds() - start));
            UnityEngine.Debug.LogWarning("end test1");
            idList = new HashSet<long>(testCnt);
            UnityEngine.Debug.LogWarning("start test2");
            start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
            for (int i = 0; i < testCnt; i++)
            {
                long id = dbid.AllocAndWaitId();
                if (id != 0 && !idList.Add(id))
                {
                    UnityEngine.Debug.LogWarning("dup :" + id);
                }
            }
            UnityEngine.Debug.LogWarning("time :" + (DateTimeOffset.Now.ToUnixTimeMilliseconds() - start));
            UnityEngine.Debug.LogWarning("end test2");

        }
    }
    
}
